package jvm.VMExecSubsystem;

/**
 * 方法动态分派演示
 * @author msi
 *  javap -verbose DynamicDispatch.class
 */
public class DynamicDispatch {

    static abstract class Human {
        protected abstract void sayHello();
    }

    static class Man extends Human {
        @Override
        protected void sayHello() {
            System.out.println("man say hello");
        }
    }

    static class Woman extends Human {
        @Override
        protected void sayHello() {
            System.out.println("woman say hello");
        }
    }

    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        man.sayHello();
        woman.sayHello();
        man = new Woman();
        man.sayHello();
    }
}
/**
 * 结果：man say hello
 *      woman say hello
 *      woman say hello
 *      因为子类重写了该方法，所以会调用子类的方法，显然这种方式和重载的字节码指令不同，重载是静态分派，重写是动态分派，那么java虚拟机是怎么区分这两种方法的呢？
 *      显然这里的解析不可能根据静态类型来判断，因为静态类型都是Human的类型，却调用了两种不同的方法实现。使用 javap 命令查看字节码可知。
 *         16: aload_1
 *         17: invokevirtual #6                  // Method jvm/VMExecSubsystem/DynamicDispatch$Human.sayHello:()V
 *         20: aload_2
 *         21: invokevirtual #6                  // Method jvm/VMExecSubsystem/DynamicDispatch$Human.sayHello:()V
 *      从上面的指令可以看出，两个对象调用的都是Human.sayHello()方法，那么为什么会有两个不同的实现呢？这就要从 invokevirtual 指令说起了。
 *      要弄懂这个指令是如何确定调用方法版本，如何实现多态查找的。根据《java虚拟机规范》， invokevritual指令的允许时解析过程分为以下几步：
 *          1.找到操作数栈顶的第一个元素所执行的对象的实际类型，记作C。 在上面的指令中 aload 指令就是将某个元素推到栈顶。
 *          2.如果在类型C中找到与常量中的描述符和简单名称都相符合的方法，则进行访问权限的校验，如果通过则返回这个方法的直接引用，查找结束，不通过则返回 java.lang.IllegalAccessError 异常。
 *          3.否则，则按继承关系从下往上依次对C的各个父类进行第二步的搜素和验证过程。
 *          4.如果始终没有找到合适的方法，则抛出 java.lang.AbstractMethodError 异常。
 *     所以重写的本质就是 invokevirtual指令的第一步是确定这个对象的实际类型。按照实际类型去匹配方法版本。这就是java中方法重写的本质。我们把这种在运行期根据实际类型执行版本的分派过程称为动态分派。
 *     既然这种多态性的根源在于虚方法调用指令 invokevirtual 的执行逻辑，那自然我们得出的结论就只会对方法生效。对字段是无效的。因为字段不使用这条指令，也就是字段永远不可能是多态的。 见代码： FieldHasNoPolymorphic.java
 */
